import * as config from "config"
import * as crypto from "crypto"
import {withLock} from "easylock";
import * as fs from "fs";
import * as path from "path";
import {InternalError} from "../api/error";
import {PaymentOrderModel} from "../database/models/paymentOrder";
import {IAuther, PaymentArguments} from "../wechat/autherInterface";
import moment = require("moment");


const FORMAT_STR = `YYYY-MM-DD HH:mm:ss`


export class AliAuther implements IAuther {

  constructor(private readonly privateRSAKey,
              private readonly publicRSAKey) {
  }


  private toFormattedString(toSingObject: any) {
    const toSignString = Object.keys(toSingObject)
      .sort()
      .map(k => {
        return `${k}=${toSingObject[k]}`
      })
      .join('&')

    return toSignString
  }


  sign(toSingObject: any): string {
    const toSignString = this.toFormattedString(toSingObject)

    const signer = crypto.createSign(`RSA-SHA256`)

    signer.update(toSignString)
    return signer.sign(this.privateRSAKey, 'base64')
  }

  verify(toVerifyObject: any): boolean {
    const {sign, sign_type, ...toSign} = toVerifyObject

    const verifier = crypto.createVerify(`RSA-SHA256`)
    verifier.update(this.toFormattedString(toSign))

    return verifier.verify(this.publicRSAKey, sign, 'base64')
  }

}


export class AliPayCashier {

  constructor(readonly appid: string,
              readonly signerToAli: IAuther,
              readonly aliVerifier: IAuther,
              readonly notfiyUrl: string
  ) {

  }


  async createPaymentOrder(paymentArg: PaymentArguments, expiredInMs: number, now: number = Date.now()) {

    const paymentOrder = new PaymentOrderModel({
      paymentType: 'aliPay',
      orderType: paymentArg.type,
      order: paymentArg.order,
      state: 'init',
      appId: this.appid,
      price: paymentArg.price,
      createAt: new Date()
    })

    const bizObj = {
      subject: paymentArg.title,
      total_amount: paymentArg.price,
      out_trade_no: paymentOrder.id,
      product_code: "QUICK_MSECURITY_PAY"
    }

    const payOrder = {
      app_id: this.appid,
      method: 'alipay.trade.app.pay',
      charset: 'utf-8',
      sign_type: 'RSA2',
      timestamp: moment().format(FORMAT_STR),
      version: '1.0',
      notify_url: this.notfiyUrl,
      biz_content: JSON.stringify(bizObj),
    }

    const sign = this.signerToAli.sign(payOrder)

    paymentOrder.rawOrder = {...payOrder, sign}
    paymentOrder.externalId = paymentOrder.id
    await paymentOrder.save()

    return {
      paymentOrder,
      prepay: paymentOrder.rawOrder
    }
  }

  async confirmPayment(confirmNotification: any, dispatcher) {

    if (this.aliVerifier.verify(confirmNotification)) {

      return withLock(confirmNotification.out_trade_no, async () => {
        const payment = await PaymentOrderModel.findById(confirmNotification.out_trade_no)

        if (!payment) {
          throw InternalError('NO_SUCH_ALI_PAYMENT')
        }

        payment.externalId = confirmNotification.trade_no
        await payment.save()

        if (confirmNotification.trade_status === 'TRADE_SUCCESS') {

          if (payment.state === 'init') {

            await dispatcher.confirm(payment)

            payment.state = 'finished'
            await payment.save()

          } else {
            throw InternalError('ALREADY_CONFIRMED_PAYMENT')
          }
        }
      })
    } else {
      console.log('BAD_ALI_PAY_NOTIFICATION', confirmNotification)
      throw InternalError('BAD_ALI_PAY_NOTIFICATION')
    }
  }
}


const appId = config.get<string>("ali.app_id")
const notifyUrl = config.get<string>("ali.notify_url")

const keyPathBase = path.join(__dirname, '..', '..', 'alipay')

export const aliPayUlongAuther = new AliAuther(
  fs.readFileSync(path.join(keyPathBase, 'rsa_private_key.pem'), 'utf-8'),
  fs.readFileSync(path.join(keyPathBase, 'rsa_public_key.pem'), 'utf-8')
)

export const aliPayServerAuther = new AliAuther(
  fs.readFileSync(path.join(keyPathBase, 'rsa_private_key.pem'), 'utf-8'),
  fs.readFileSync(path.join(keyPathBase, 'alipay_public_key.pem'), 'utf-8')
)

export const alipayCashier = new AliPayCashier(
  appId, aliPayUlongAuther, aliPayServerAuther,
  notifyUrl,
)
